// ========================================================================
// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
// You may elect to redistribute this code under either of these licenses.
// ========================================================================

package org.eclipse.jetty.server.ssl;

import java.io.IOException;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;

import org.eclipse.jetty.http.HttpParser;
import org.eclipse.jetty.http.HttpSchemes;
import org.eclipse.jetty.http.ssl.SslContextFactory;
import org.eclipse.jetty.io.Buffers;
import org.eclipse.jetty.io.Buffers.Type;
import org.eclipse.jetty.io.BuffersFactory;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.bio.SocketEndPoint;
import org.eclipse.jetty.io.nio.SelectChannelEndPoint;
import org.eclipse.jetty.io.nio.SelectorManager.SelectSet;
import org.eclipse.jetty.io.nio.SslSelectChannelEndPoint;
import org.eclipse.jetty.server.HttpConnection;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.nio.SelectChannelConnector;

/* ------------------------------------------------------------ */
/**
 * SslSelectChannelConnector.
 * 
 * @org.apache.xbean.XBean element="sslConnector" description="Creates an NIO ssl connector"
 */
public class SslSelectChannelConnector extends SelectChannelConnector implements SslConnector
{

	private final SslContextFactory _sslContextFactory;
	private Buffers _sslBuffers;

	/* ------------------------------------------------------------ */
	public SslSelectChannelConnector()
	{
		this(new SslContextFactory(SslContextFactory.DEFAULT_KEYSTORE_PATH));
	}

	/* ------------------------------------------------------------ */
	public SslSelectChannelConnector(SslContextFactory sslContextFactory)
	{
		_sslContextFactory = sslContextFactory;
		setUseDirectBuffers(false);
	}

	/* ------------------------------------------------------------ */
	/**
	 * Allow the Listener a chance to customise the request. before the server does its stuff. <br>
	 * This allows the required attributes to be set for SSL requests. <br>
	 * The requirements of the Servlet specs are:
	 * <ul>
	 * <li>an attribute named "javax.servlet.request.ssl_session_id" of type String (since Servlet Spec 3.0).</li>
	 * <li>an attribute named "javax.servlet.request.cipher_suite" of type String.</li>
	 * <li>an attribute named "javax.servlet.request.key_size" of type Integer.</li>
	 * <li>an attribute named "javax.servlet.request.X509Certificate" of type java.security.cert.X509Certificate[]. This is an array of objects of type X509Certificate, the order of this array is defined as being in ascending order of trust. The first certificate in the chain is the one set by the client, the next is the one used to authenticate the first, and so on.</li>
	 * </ul>
	 * 
	 * @param endpoint The Socket the request arrived on. This should be a {@link SocketEndPoint} wrapping a {@link SSLSocket}.
	 * @param request HttpRequest to be customised.
	 */
	@Override
	public void customize(EndPoint endpoint, Request request) throws IOException
	{
		request.setScheme(HttpSchemes.HTTPS);
		super.customize(endpoint, request);

		SslSelectChannelEndPoint sslHttpChannelEndpoint = (SslSelectChannelEndPoint)endpoint;
		SSLEngine sslEngine = sslHttpChannelEndpoint.getSSLEngine();
		SSLSession sslSession = sslEngine.getSession();

		SslCertificates.customize(sslSession, endpoint, request);
	}

	/* ------------------------------------------------------------ */
	/**
	 * @return True if SSL re-negotiation is allowed (default false)
	 * @deprecated
	 */
	@Deprecated
	public boolean isAllowRenegotiate()
	{
		return _sslContextFactory.isAllowRenegotiate();
	}

	/* ------------------------------------------------------------ */
	/**
	 * Set if SSL re-negotiation is allowed. CVE-2009-3555 discovered a vulnerability in SSL/TLS with re-negotiation. If your JVM does not have CVE-2009-3555 fixed, then re-negotiation should not be allowed. CVE-2009-3555 was fixed in Sun java 1.6 with a ban of renegotiate in u19 and with RFC5746 in u22.
	 * 
	 * @param allowRenegotiate true if re-negotiation is allowed (default false)
	 * @deprecated
	 */
	@Deprecated
	public void setAllowRenegotiate(boolean allowRenegotiate)
	{
		_sslContextFactory.setAllowRenegotiate(allowRenegotiate);
	}

	/* ------------------------------------------------------------ */
	/**
	 * @see org.eclipse.jetty.server.ssl.SslConnector#getExcludeCipherSuites()
	 * @deprecated
	 */
	@Deprecated
	public String[] getExcludeCipherSuites()
	{
		return _sslContextFactory.getExcludeCipherSuites();
	}

	/* ------------------------------------------------------------ */
	/**
	 * @see org.eclipse.jetty.server.ssl.SslConnector#setExcludeCipherSuites(java.lang.String[])
	 * @deprecated
	 */
	@Deprecated
	public void setExcludeCipherSuites(String[] cipherSuites)
	{
		_sslContextFactory.setExcludeCipherSuites(cipherSuites);
	}

	/* ------------------------------------------------------------ */
	/**
	 * @see org.eclipse.jetty.server.ssl.SslConnector#getExcludeCipherSuites()
	 * @deprecated
	 */
	@Deprecated
	public String[] getIncludeCipherSuites()
	{
		return _sslContextFactory.getIncludeCipherSuites();
	}

	/* ------------------------------------------------------------ */
	/**
	 * @see org.eclipse.jetty.server.ssl.SslConnector#setExcludeCipherSuites(java.lang.String[])
	 * @deprecated
	 */
	@Deprecated
	public void setIncludeCipherSuites(String[] cipherSuites)
	{
		_sslContextFactory.setIncludeCipherSuites(cipherSuites);
	}

	/* ------------------------------------------------------------ */
	/**
	 * @see org.eclipse.jetty.server.ssl.SslConnector#setPassword(java.lang.String)
	 * @deprecated
	 */
	@Deprecated
	public void setPassword(String password)
	{
		_sslContextFactory.setKeyStorePassword(password);
	}

	/* ------------------------------------------------------------ */
	/**
	 * @see org.eclipse.jetty.server.ssl.SslConnector#setTrustPassword(java.lang.String)
	 * @deprecated
	 */
	@Deprecated
	public void setTrustPassword(String password)
	{
		_sslContextFactory.setTrustStorePassword(password);
	}

	/* ------------------------------------------------------------ */
	/**
	 * @see org.eclipse.jetty.server.ssl.SslConnector#setKeyPassword(java.lang.String)
	 * @deprecated
	 */
	@Deprecated
	public void setKeyPassword(String password)
	{
		_sslContextFactory.setKeyManagerPassword(password);
	}

	/* ------------------------------------------------------------ */
	/**
	 * Unsupported. TODO: we should remove this as it is no longer an overridden method from SslConnector (like it was in the past)
	 * 
	 * @deprecated
	 */
	@Deprecated
	public String getAlgorithm()
	{
		throw new UnsupportedOperationException();
	}

	/* ------------------------------------------------------------ */
	/**
	 * Unsupported. TODO: we should remove this as it is no longer an overridden method from SslConnector (like it was in the past)
	 * 
	 * @deprecated
	 */
	@Deprecated
	public void setAlgorithm(String algorithm)
	{
		throw new UnsupportedOperationException();
	}

	/* ------------------------------------------------------------ */
	/**
	 * @see org.eclipse.jetty.server.ssl.SslConnector#getProtocol()
	 * @deprecated
	 */
	@Deprecated
	public String getProtocol()
	{
		return _sslContextFactory.getProtocol();
	}

	/* ------------------------------------------------------------ */
	/**
	 * @see org.eclipse.jetty.server.ssl.SslConnector#setProtocol(java.lang.String)
	 * @deprecated
	 */
	@Deprecated
	public void setProtocol(String protocol)
	{
		_sslContextFactory.setProtocol(protocol);
	}

	/* ------------------------------------------------------------ */
	/**
	 * @see org.eclipse.jetty.server.ssl.SslConnector#setKeystore(java.lang.String)
	 * @deprecated
	 */
	@Deprecated
	public void setKeystore(String keystore)
	{
		_sslContextFactory.setKeyStore(keystore);
	}

	/* ------------------------------------------------------------ */
	/**
	 * @see org.eclipse.jetty.server.ssl.SslConnector#getKeystore()
	 * @deprecated
	 */
	@Deprecated
	public String getKeystore()
	{
		return _sslContextFactory.getKeyStore();
	}

	/* ------------------------------------------------------------ */
	/**
	 * @see org.eclipse.jetty.server.ssl.SslConnector#getKeystoreType()
	 * @deprecated
	 */
	@Deprecated
	public String getKeystoreType()
	{
		return _sslContextFactory.getKeyStoreType();
	}

	/* ------------------------------------------------------------ */
	/**
	 * @see org.eclipse.jetty.server.ssl.SslConnector#getNeedClientAuth()
	 * @deprecated
	 */
	@Deprecated
	public boolean getNeedClientAuth()
	{
		return _sslContextFactory.getNeedClientAuth();
	}

	/* ------------------------------------------------------------ */
	/**
	 * @see org.eclipse.jetty.server.ssl.SslConnector#getWantClientAuth()
	 * @deprecated
	 */
	@Deprecated
	public boolean getWantClientAuth()
	{
		return _sslContextFactory.getWantClientAuth();
	}

	/* ------------------------------------------------------------ */
	/**
	 * @see org.eclipse.jetty.server.ssl.SslConnector#setNeedClientAuth(boolean)
	 * @deprecated
	 */
	@Deprecated
	public void setNeedClientAuth(boolean needClientAuth)
	{
		_sslContextFactory.setNeedClientAuth(needClientAuth);
	}

	/* ------------------------------------------------------------ */
	/**
	 * @see org.eclipse.jetty.server.ssl.SslConnector#setWantClientAuth(boolean)
	 * @deprecated
	 */
	@Deprecated
	public void setWantClientAuth(boolean wantClientAuth)
	{
		_sslContextFactory.setWantClientAuth(wantClientAuth);
	}

	/* ------------------------------------------------------------ */
	/**
	 * @see org.eclipse.jetty.server.ssl.SslConnector#setKeystoreType(java.lang.String)
	 * @deprecated
	 */
	@Deprecated
	public void setKeystoreType(String keystoreType)
	{
		_sslContextFactory.setKeyStoreType(keystoreType);
	}

	/* ------------------------------------------------------------ */
	/**
	 * @see org.eclipse.jetty.server.ssl.SslConnector#getProvider()
	 * @deprecated
	 */
	@Deprecated
	public String getProvider()
	{
		return _sslContextFactory.getProvider();
	}

	/* ------------------------------------------------------------ */
	/**
	 * @see org.eclipse.jetty.server.ssl.SslConnector#getSecureRandomAlgorithm()
	 * @deprecated
	 */
	@Deprecated
	public String getSecureRandomAlgorithm()
	{
		return _sslContextFactory.getSecureRandomAlgorithm();
	}

	/* ------------------------------------------------------------ */
	/**
	 * @see org.eclipse.jetty.server.ssl.SslConnector#getSslKeyManagerFactoryAlgorithm()
	 * @deprecated
	 */
	@Deprecated
	public String getSslKeyManagerFactoryAlgorithm()
	{
		return _sslContextFactory.getSslKeyManagerFactoryAlgorithm();
	}

	/* ------------------------------------------------------------ */
	/**
	 * @see org.eclipse.jetty.server.ssl.SslConnector#getSslTrustManagerFactoryAlgorithm()
	 * @deprecated
	 */
	@Deprecated
	public String getSslTrustManagerFactoryAlgorithm()
	{
		return _sslContextFactory.getTrustManagerFactoryAlgorithm();
	}

	/* ------------------------------------------------------------ */
	/**
	 * @see org.eclipse.jetty.server.ssl.SslConnector#getTruststore()
	 * @deprecated
	 */
	@Deprecated
	public String getTruststore()
	{
		return _sslContextFactory.getTrustStore();
	}

	/* ------------------------------------------------------------ */
	/**
	 * @see org.eclipse.jetty.server.ssl.SslConnector#getTruststoreType()
	 * @deprecated
	 */
	@Deprecated
	public String getTruststoreType()
	{
		return _sslContextFactory.getTrustStoreType();
	}

	/* ------------------------------------------------------------ */
	/**
	 * @see org.eclipse.jetty.server.ssl.SslConnector#setProvider(java.lang.String)
	 * @deprecated
	 */
	@Deprecated
	public void setProvider(String provider)
	{
		_sslContextFactory.setProvider(provider);
	}

	/* ------------------------------------------------------------ */
	/**
	 * @see org.eclipse.jetty.server.ssl.SslConnector#setSecureRandomAlgorithm(java.lang.String)
	 * @deprecated
	 */
	@Deprecated
	public void setSecureRandomAlgorithm(String algorithm)
	{
		_sslContextFactory.setSecureRandomAlgorithm(algorithm);
	}

	/* ------------------------------------------------------------ */
	/**
	 * @see org.eclipse.jetty.server.ssl.SslConnector#setSslKeyManagerFactoryAlgorithm(java.lang.String)
	 * @deprecated
	 */
	@Deprecated
	public void setSslKeyManagerFactoryAlgorithm(String algorithm)
	{
		_sslContextFactory.setSslKeyManagerFactoryAlgorithm(algorithm);
	}

	/* ------------------------------------------------------------ */
	/**
	 * @see org.eclipse.jetty.server.ssl.SslConnector#setSslTrustManagerFactoryAlgorithm(java.lang.String)
	 * @deprecated
	 */
	@Deprecated
	public void setSslTrustManagerFactoryAlgorithm(String algorithm)
	{
		_sslContextFactory.setTrustManagerFactoryAlgorithm(algorithm);
	}

	/* ------------------------------------------------------------ */
	/**
	 * @see org.eclipse.jetty.server.ssl.SslConnector#setTruststore(java.lang.String)
	 * @deprecated
	 */
	@Deprecated
	public void setTruststore(String truststore)
	{
		_sslContextFactory.setTrustStore(truststore);
	}

	/* ------------------------------------------------------------ */
	/**
	 * @see org.eclipse.jetty.server.ssl.SslConnector#setTruststoreType(java.lang.String)
	 * @deprecated
	 */
	@Deprecated
	public void setTruststoreType(String truststoreType)
	{
		_sslContextFactory.setTrustStoreType(truststoreType);
	}

	/* ------------------------------------------------------------ */
	/**
	 * @see org.eclipse.jetty.server.ssl.SslConnector#setSslContext(javax.net.ssl.SSLContext)
	 * @deprecated
	 */
	@Deprecated
	public void setSslContext(SSLContext sslContext)
	{
		_sslContextFactory.setSslContext(sslContext);
	}

	/* ------------------------------------------------------------ */
	/**
	 * @see org.eclipse.jetty.server.ssl.SslConnector#setSslContext(javax.net.ssl.SSLContext)
	 * @deprecated
	 */
	@Deprecated
	public SSLContext getSslContext()
	{
		return _sslContextFactory.getSslContext();
	}

	/* ------------------------------------------------------------ */
	/**
	 * @see org.eclipse.jetty.server.ssl.SslConnector#getSslContextFactory()
	 */
	public SslContextFactory getSslContextFactory()
	{
		return _sslContextFactory;
	}

	/* ------------------------------------------------------------ */
	/**
	 * By default, we're confidential, given we speak SSL. But, if we've been told about an confidential port, and said port is not our port, then we're not. This allows separation of listeners providing INTEGRAL versus CONFIDENTIAL constraints, such as one SSL listener configured to require client certs providing CONFIDENTIAL, whereas another SSL listener not requiring client certs providing mere INTEGRAL constraints.
	 */
	@Override
	public boolean isConfidential(Request request)
	{
		final int confidentialPort = getConfidentialPort();
		return confidentialPort == 0 || confidentialPort == request.getServerPort();
	}

	/* ------------------------------------------------------------ */
	/**
	 * By default, we're integral, given we speak SSL. But, if we've been told about an integral port, and said port is not our port, then we're not. This allows separation of listeners providing INTEGRAL versus CONFIDENTIAL constraints, such as one SSL listener configured to require client certs providing CONFIDENTIAL, whereas another SSL listener not requiring client certs providing mere INTEGRAL constraints.
	 */
	@Override
	public boolean isIntegral(Request request)
	{
		final int integralPort = getIntegralPort();
		return integralPort == 0 || integralPort == request.getServerPort();
	}

	/* ------------------------------------------------------------------------------- */
	@Override
	protected SelectChannelEndPoint newEndPoint(SocketChannel channel, SelectSet selectSet, SelectionKey key) throws IOException
	{
		SSLEngine engine = createSSLEngine(channel);
		SslSelectChannelEndPoint endp = new SslSelectChannelEndPoint(_sslBuffers, channel, selectSet, key, engine, SslSelectChannelConnector.this._maxIdleTime);
		endp.setAllowRenegotiate(_sslContextFactory.isAllowRenegotiate());
		return endp;
	}

	/* ------------------------------------------------------------------------------- */
	@Override
	protected Connection newConnection(SocketChannel channel, SelectChannelEndPoint endpoint)
	{
		HttpConnection connection = (HttpConnection)super.newConnection(channel, endpoint);
		((HttpParser)connection.getParser()).setForceContentBuffer(true);
		return connection;
	}

	/* ------------------------------------------------------------ */
	/**
	 * @param channel A channel which if passed is used as to extract remote host and port for the purposes of SSL session caching
	 * @return A SSLEngine for a new or cached SSL Session
	 * @throws IOException if the SSLEngine cannot be created
	 */
	protected SSLEngine createSSLEngine(SocketChannel channel) throws IOException
	{
		SSLEngine engine;
		if (channel != null && _sslContextFactory.isSessionCachingEnabled())
		{
			String peerHost = channel.socket().getInetAddress().getHostAddress();
			int peerPort = channel.socket().getPort();
			engine = _sslContextFactory.getSslContext().createSSLEngine(peerHost, peerPort);
		}
		else
		{
			engine = _sslContextFactory.getSslContext().createSSLEngine();
		}
		customizeEngine(engine);
		return engine;
	}

	/* ------------------------------------------------------------ */
	private void customizeEngine(SSLEngine engine)
	{
		engine.setUseClientMode(false);

		if (_sslContextFactory.getWantClientAuth())
			engine.setWantClientAuth(_sslContextFactory.getWantClientAuth());
		if (_sslContextFactory.getNeedClientAuth())
			engine.setNeedClientAuth(_sslContextFactory.getNeedClientAuth());

		engine.setEnabledCipherSuites(
			_sslContextFactory.selectCipherSuites(engine.getEnabledCipherSuites(),
				engine.getSupportedCipherSuites()));
	}

	/* ------------------------------------------------------------ */
	/**
	 * @see org.eclipse.jetty.server.nio.SelectChannelConnector#doStart()
	 */
	@Override
	protected void doStart() throws Exception
	{
		if (!_sslContextFactory.checkConfig())
		{
			throw new IllegalStateException("SSL context is not configured correctly.");
		}

		_sslContextFactory.start();

		SSLEngine sslEngine = _sslContextFactory.getSslContext().createSSLEngine();

		sslEngine.setUseClientMode(false);
		sslEngine.setWantClientAuth(_sslContextFactory.getWantClientAuth());
		sslEngine.setNeedClientAuth(_sslContextFactory.getNeedClientAuth());

		sslEngine.setEnabledCipherSuites(_sslContextFactory.selectCipherSuites(
			sslEngine.getEnabledCipherSuites(),
			sslEngine.getSupportedCipherSuites()));

		SSLSession sslSession = sslEngine.getSession();

		_sslBuffers = BuffersFactory.newBuffers(
			getUseDirectBuffers() ? Type.DIRECT : Type.INDIRECT, sslSession.getApplicationBufferSize(),
			getUseDirectBuffers() ? Type.DIRECT : Type.INDIRECT, sslSession.getApplicationBufferSize(),
			getUseDirectBuffers() ? Type.DIRECT : Type.INDIRECT, getMaxBuffers()
			);

		if (getRequestHeaderSize() < sslSession.getApplicationBufferSize())
			setRequestHeaderSize(sslSession.getApplicationBufferSize());
		if (getRequestBufferSize() < sslSession.getApplicationBufferSize())
			setRequestBufferSize(sslSession.getApplicationBufferSize());

		super.doStart();
	}

	/* ------------------------------------------------------------ */
	/**
	 * @see org.eclipse.jetty.server.nio.SelectChannelConnector#doStop()
	 */
	@Override
	protected void doStop() throws Exception
	{
		_sslContextFactory.stop();
		_sslBuffers = null;
		super.doStop();
	}

	/* ------------------------------------------------------------ */
	/**
	 * @return SSL buffers
	 */
	public Buffers getSslBuffers()
	{
		return _sslBuffers;
	}
}
